/**
 * \file Profiler.h
 * \brief Profiler declaration class
 * \author Edouard ROGE
 * \date 16 October 2012
 */

#ifndef _PROFILER_
#define _PROFILER_

#include <thread>
#include <vector>
#include <cstring>
#include <stdio.h>
#include "Core/Time/TimeMeasure.h"
#include "Core/Pattern/Singleton.h"
#include "Core/Define/Define.h"
#include "Core/Memory/HeapObject.h"


namespace Core
{
	namespace Profile
	{
		/**
		 * \class Profiler
		 * \brief Store profiling informations and write them into a file at the end of the program
		 */
		class Profiler : public Singleton<Profiler>
		{
		public:

			/**
			 * \fn Profiler();
			 * \brief Default constructor
			 */
			Profiler();

			/**
			 * \fn Profiler(const std::string & _OutputFilename);
			 * \brief Constructor with output filename in parameter
			 * \param _OutputFilename output filename
			 */
			Profiler(const std::string & _OutputFilename);

			/**
			 * \fn virtual ~Profiler();
			 * \brief Destructor
			 */
			virtual ~Profiler();

			/**
			 * \fn uint32 StartNewTimeMeasure(const char * _strName, std::thread::id _iThreadId, std::thread::id _iParentThreadId);
			 * \param _strName function to profile name
			 * \param _iThreadId thread id of the function
			 * \param _iParentThreadId parent thread id
			 * \return index
			 * \brief Start profiling a piece of code
			 */
			uint32 StartNewTimeMeasure(const char * _strName, std::thread::id _iThreadId, std::thread::id _iParentThreadId);

			/**
			 * \fn uint32 StartNewTimeMeasure(const char * _strName, std::thread::id _iThreadId);
			 * \param _strName function to profile name
			 * \param _iThreadId thread id of the function
			 * \return index
			 * \brief Start profiling a piece of code
			 */
			uint32 StartNewTimeMeasure(const char * _strName, std::thread::id _iThreadId);

			/**
			 * \fn void StopTimeMeasure(uint32 _iIndex, uint32 _iThreadId);
			 * \param _iIndex index given by the StartNewTimeMeasure function
			 * \param _iThreadId thread id of the function
			 * \brief Stop profiling a piece of code
			 */
			void StopTimeMeasure(uint32 _iIndex, std::thread::id _iThreadId);

			/**
			 * \fn void SetOutputFilename(const std::string & _OutputFilename);
			 * \param _OutputFilename output filename
			 * \brief Set the filename where profile infos are written
			 */
			void SetOutputFilename(const std::string & _OutputFilename);

		private:

			/**
			 * \struct Node
			 * \brief Profiling node that contains informations
			 */
			struct Node
			{
				/** Parent node */
				Node * parent;

				/** Child node */
				Node * child;

				/** Sister node, equivalent to previous node */
				Node * sister;

				/** Brother node, equivalent to next node */
				Node * brother;

				/** Number of calls for this function */
				uint32 nCalls;

				/** Cumulated time spent into this function */
				float cumulatedTime;

				/** Name of the function */
				char name[32];

				/** Thread Id of the function where it's executed */
				std::thread::id threadId;

				/**
				 * \fn Node()
				 * \brief Constructor
				 */
				Node() : parent(NULL), child(NULL), sister(NULL), brother(NULL), nCalls(0), cumulatedTime(0.f)
				{
				}

				/**
				 * \fn void AddChild(Node * _pNode)
				 * \param _pNode Node to add as a child
				 * \brief Add a node as a child
				 */
				void AddChild(Node * _pNode)
				{
					if (!child)
					{
						child = _pNode;
					}
					else
					{
						Node * pCurr = child;
						while (pCurr->brother != NULL)
						{
							pCurr = pCurr->brother;
						}
						pCurr->brother = _pNode;
						_pNode->sister = pCurr;
					}
					_pNode->parent = this;
				}

				/**
				 * \fn Node * GetChild(const char * _strName, uint32 _iThreadId)
				 * \param _strName name of the child to retrieve
				 * \param _iThreadId thread id of the child to retrieve
				 * \return the wanted child, NULL if not found
				 * \brief retreive a child node
				 */
				Node * GetChild(const char * _strName, std::thread::id _iThreadId)
				{
					Node * pCurr = child;
					while (pCurr)
					{
						if (strcmp(_strName, pCurr->name) == 0 && _iThreadId == pCurr->threadId)
							return pCurr;
						pCurr = pCurr->brother;
					}
					return NULL;
				}
			};

			/**
			 * \fn void WriteDataNode(Node * _pNode, uint32 _depth, FILE * _pFile);
			 * \param _pNode node to write
			 * \param _depth depth of the node in the tree
			 * \param _pFile file to write in
			 * \brief write profiled node and its children in a file
			 */
			void WriteDataNode(Node * _pNode, uint32 _depth, FILE * _pFile);

			/**
			 * \fn void DeleteNodes(Node * _pNode);
			 * \param _pNode node to remove
			 * \brief remove node from the profiling
			 */
			void DeleteNodes(Node * _pNode);

			/**
			 * \fn Node * GetCurrentNodeFromThreadId(std::thread::id _iThreadId) const;
			 * \param _iThreadId thread id of the node to get
			 * \return the node if found, otherwise NULL
			 * \brief Get a node from the current node list
			 */
			Node * GetCurrentNodeFromThreadId(std::thread::id _iThreadId) const;

			/** root node of the measure */
			Node * m_pRoot;

			/** to measure time */
			Time::TimeMeasure m_TimeMeasure;

			/** current nodes (due to multithreading) */
			std::vector<Node *> m_pCurrentNodes;

			/** mutex to avoid concurrent access */
			std::mutex m_mutex;

			/** output filename */
			std::string m_OutputFilename;
		};
	}
}

#endif // _PROFILER_
